連續 22 天了,今天把前幾天踩到的坑一次收尾:
1. 解掉 AsyncWebhookHandler 不存在的錯誤、
2. 讓金價與彩券查詢更穩、
3. 語音 TTS/STT 有雲端與免費備援、
4. 部署在 Render 的健康檢查與環境變數清單。
目標是:線上機器不再紅字爆炸,指令都能回、服務穩穩跑。
⸻
前情提要
昨天你在 Render 的日誌看到:
• ImportError: cannot import name 'AsyncWebhookHandler' from 'linebot.v3.webhooks'
• 金價查詢報 AttributeError: 'NoneType' object has no attribute 'find'
• Groq 主模型被下架
• OpenAI API Key 無效導致 TTS/STT 失敗
今天的文章就把 Handler/爬蟲/模型/語音/部署 一次收尾。
⸻
症狀
你換到 line-bot-sdk v3(或 3.19.0)後,嘗試匯入:
from linebot.v3.webhooks import AsyncWebhookHandler
結果直接噴:
ImportError: cannot import name 'AsyncWebhookHandler'
原因
目前該版本 沒有 AsyncWebhookHandler。官方穩定做法是用 同步的 WebhookHandler,在 FastAPI 端點裡把 handler.handle(...) 丟到 threadpool,就不會卡 event loop。
修法(精華)
from linebot.v3 import WebhookHandler
handler = WebhookHandler(CHANNEL_SECRET)
@router.post("/callback")
async def callback(request: Request):
signature = request.headers.get("X-Line-Signature", "")
body = await request.body()
# 把同步的 handler 放到 thread pool 避免阻塞
await run_in_threadpool(handler.handle, body.decode("utf-8"), signature)
return JSONResponse({"status": "ok"})
這樣既保留 v3 SDK 的優點,也符合 FastAPI 的 async life-cycle,不會再遇到 import 爆炸。
⸻
台銀金價頁面常改 class 或 table 結構,與其黏死 DOM,不如解析整頁文字。
關鍵作法
1. BeautifulSoup(...).get_text(" ", strip=True) 把頁面變成一條字串。
2. 用 regex 擷取:
• 掛牌時間
• 本行買進、本行賣出
3. 額外計算買賣價差,順便標註「盤整/偏寬/價差偏大」。
核心程式(摘錄)
BOT_GOLD_URL = "https://rate.bot.com.tw/gold?Lang=zh-TW"
HEADERS = {"User-Agent": "Mozilla/5.0 ..."}
def parse_bot_gold_text(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
text = soup.get_text(" ", strip=True)
m_time = re.search(r"掛牌時間[::]\s*(\d{4}/\d{2}/\d{2}\s+\d{2}:\d{2})", text)
m_sell = re.search(r"本行賣出\s*([\d,]+(?:\.\d+)?)", text)
m_buy = re.search(r"本行買進\s*([\d,]+(?:\.\d+)?)", text)
if not (m_sell and m_buy):
raise RuntimeError("找不到『本行賣出/本行買進』欄位")
return {
"listed_at": m_time.group(1) if m_time else None,
"sell": float(m_sell.group(1).replace(",", "")),
"buy": float(m_buy.group(1).replace(",", "")),
}
def get_gold_analysis() -> str:
r = requests.get(BOT_GOLD_URL, headers=HEADERS, timeout=10)
r.raise_for_status()
d = parse_bot_gold_text(r.text)
spread = d["sell"] - d["buy"]
bias = "盤整" if spread <= 30 else ("偏寬" if spread <= 60 else "價差偏大")
return (
f"**金價快報(台灣銀行)**\n"
f"- 掛牌時間:{d['listed_at'] or '(頁面未標示)'}\n"
f"- 本行賣出(1克):**{d['sell']:,.0f} 元**\n"
f"- 本行買進(1克):**{d['buy']:,.0f} 元**\n"
f"- 買賣價差:{spread:,.0f} 元({bias})\n"
f"\n資料來源:{BOT_GOLD_URL}"
)
這招把「找不到 table」的 bug 徹底避開,穩!
⸻
需求
• 支援 大樂透、威力彩、539
• 優先用你專案裡的 TaiwanLotteryCrawler;失敗再用官方頁面文字解析(regex)
• 附加 財神方位(若爬不到就帶上友善的 fallback 字樣)
流程
1. try: 自訂 crawler → 成功就拿資料
2. except: → fallback_scrape(kind) 去台彩官網抓字串
3. 丟給 LLM 產出:趨勢重點 + 熱冷號 + 3 組推薦號碼(符合規則)
使用方式
• LINE 訊息輸入:大樂透 / 威力彩 / 539
• 機器人回一則分析與推薦(含注意風險)
⸻
亮點
• 識別 2330、00937B、NVDA、^TWII/^GSPC
• 優先用 yfinance 的 fast_info + history,抓名字、現價、漲跌幅
• 若 00937B 等拿不到,fallback 到 YahooStock(如果你專案有)
• 可接你現成模組:stock_price / stock_news / stock_fundamental / stock_dividend
• 整理成一篇 Markdown 報告(技術面/基本面/消息面/建議區間/停利目標)
指令
• 2330、NVDA、台股大盤、美股大盤
⸻
實務部署最怕 API key 失效或額度用完,有備援就不會整個功能掛掉。
⸻
你的日誌明確提示:
The model llama-3.1-70b-versatile
has been decommissioned
請改用: llama-3.3-70b-versatile(或你 Console 推薦的最新)
並保留 fallback:llama-3.1-8b-instant(或更換成 3.2/3.3 的 8b 即時模型)。
⸻
必填環境變數
• BASE_URL:你的服務 URL(不含 /callback)
• CHANNEL_ACCESS_TOKEN
• CHANNEL_SECRET
• GROQ_API_KEY
選填(有就上,不影響啟動)
• OPENAI_API_KEY
• CLOUDINARY_URL(形如 cloudinary://:@<cloud_name>)
• TTS_PROVIDER:auto / openai / gtts
健康檢查
• 暴露 GET / 與 GET /healthz → 回 200 OK
• 啟動時把 LINE Webhook 指到 BASE_URL/callback(有做兩個端點嘗試)
⸻
⸻
⸻
明日預告(Day 23)
• 加上「使用者自訂快捷鍵/巨集指令」
• 新增「一鍵產出查價比較圖表(matplotlib)」
• 佈署前一鍵自測(健康檢查腳本 + webhook 校驗)
⸻
TL;DR